#!/usr/bin/wish

#main window setup
tk scaling 1.2
wm title . "nvramtool-tk"
wm resizable . 0 0

#just in case...
set editorVar ""

proc applyOption { var } {
	
	global editorVar
	
	#invoke nvramtool for change option's value
	exec nvramtool -w "$var = $editorVar"
	
	#close editor
	if { [winfo exists .editor] } { destroy .editor }
	loadOptions
}

proc editOption { var val } {
	
	if { [winfo exists .editor] } { return }
	
	global editorVar
	set editorVar $val
	
	#create editor gui
	toplevel .editor
	wm title .editor "$var"
	wm resizable .editor 0 0
	
	ttk::labelframe .editor.frame -text "$var"
	grid .editor.frame -row 0 -column 0 -padx 5 -pady 3
	
	ttk::frame  .editor.confirm
	ttk::button .editor.confirm.cancel -text "Cancel" -command { destroy .editor }
	ttk::button .editor.confirm.apply  -text "Apply"  -command "applyOption {$var}"
	
	grid .editor.confirm        -row 1 -column 0
	grid .editor.confirm.cancel -row 0 -column 0 -pady 5 -padx 5
	grid .editor.confirm.apply  -row 0 -column 1 -pady 5 -padx 5
	
	#ask nvramtool for possible values for current option
	set cmd_output [exec nvramtool -e $var]
	
	set if_match [regexp "^Parameter $var requires a (\[A-Za-z0-9_\\- \]*)" $cmd_output full varType]
	if { $if_match } {
		
		#if it is integer, create text input
		ttk::entry .editor.frame.entry -textvariable editorVar
		ttk::label .editor.frame.type  -text "$varType"
		grid .editor.frame.entry -row 0 -column 0 -pady 1 -padx 5
		grid .editor.frame.type  -row 1 -column 0 -pady 1
		
	} else {
		
		set radiono 0
		set colno 0
		set rowno 0
		
		#else create radio buttons
		set lines [split $cmd_output \n]
		foreach line $lines {
			ttk::radiobutton .editor.frame.radio$radiono -text "$line" -value "$line" -variable editorVar
			grid .editor.frame.radio$radiono -row $rowno -column $colno -padx 3 -pady 1 -sticky w
			
			incr radiono
			if { $colno < 2 } {
				incr colno
			} else {
				set colno 0
				incr rowno
			}
		}
	}
	
	#switch focus to new window
	raise .editor
	focus -force .editor
}

proc parseOptions { data } {
	
	#remove options buttons from main window if exist
	if { [winfo exists .options] } { 
		destroy .options
	}
	
	ttk::frame .options -padding 5
	grid .options -row 5 -column 0
	
	set optno 0
	set colno 0
	set rowno 0
	
	#show buttons in main window for each option
	set lines [split $data \n]
	foreach line $lines {
		set if_match [regexp {^([A-Za-z0-9_]*) = ([A-Za-z0-9_ ]*)} $line full var val]
		if { $if_match } {
			ttk::button .options.opt$optno -text "$var ($val)" -padding { 10 2 10 2 } -command "editOption {$var} {$val}"
			grid .options.opt$optno -column $colno -row $rowno -pady 2 -padx 2 -sticky we
			
			incr optno
			if { $colno == 0 } {
				incr colno
			} else {
				incr rowno
				set colno 0
			}
		}
	}
	
	
	#show small tip for user
	if { ! [winfo exist .notice] } {
		ttk::label .notice -text "Note: All changes will be applied after reboot." -padding { 5 0 5 5 }
		grid .notice -row 10 -column 0
	}
}

proc checksumFix { newVal } {
	
	#invoke nvramtool for setup new checksum value and close alert window
	puts "Setting ug the new CMOS checksum: $newVal"
	exec nvramtool -c "$newVal"
	destroy .checksumAlert
}

proc checksumAlert { computed stored } {
	
	if { [winfo exists .checksumAlert] } { return }
	
	#create alert window
	toplevel .checksumAlert
	wm title .checksumAlert "Warning"
	wm resizable .checksumAlert 0 0
	
	ttk::label      .checksumAlert.msg     -text "CMOS checksum mismatch.\nComputed: $computed\nStored: $stored"
	ttk::labelframe .checksumAlert.buttons -text "What to do?"
	grid .checksumAlert.msg     -row 0 -column 0 -pady 3 -padx 5
	grid .checksumAlert.buttons -row 1 -column 0 -pady 5 -padx 5
	
	ttk::button .checksumAlert.buttons.close -text "Don't touch it" -command { destroy .checksumAlert }
	ttk::button .checksumAlert.buttons.fix   -text "Fix it"         -command "checksumFix $computed"
	grid .checksumAlert.buttons.close -row 0 -column 0 -padx 7 -pady 3
	grid .checksumAlert.buttons.fix   -row 0 -column 1 -padx 7 -pady 3
	
	#switch focus
	raise .checksumAlert
	focus -force .checksumAlert
	
}

proc showErrorAndExit { msg } {
	
	ttk::label  .errmsg -text "$msg"
	ttk::button .exitbt -text "Exit" -command { destroy . }
	grid .errmsg -row 0 -column 0 -pady 2 -padx 5
	grid .exitbt -row 1 -column 0 -pady 5
	
	puts $msg
}

proc loadOptions { } {

	try {
		
		#try to invoke nvramtool and get all options and current values
		set cmd_output [exec nvramtool -a]
		parseOptions $cmd_output
		
	} trap CHILDSTATUS { output } {
		
		set if_match [regexp {Warning: coreboot CMOS checksum is bad.\nComputed checksum: ([a-z0-9]*). Stored checksum: ([a-z0-9]*)} $output full computed stored]
		if { $if_match } {
			
			#CMOS checksum mismatch
			parseOptions $output
			checksumAlert $computed $stored
			
		} else {
			
			#any other error
			showErrorAndExit "$output"
			
		}
		
	} trap { POSIX ENOENT } {} {
		
		#if nvramtool not found
		showErrorAndExit "nvramtool not found. Install it and try again."
		
	}
}

#bind keys
bind . <Control-q> { destroy . }

#invoke nvramtool and show options
loadOptions
